#include <semLib.h>
#include <msgQLib.h>
#include <taskLib.h>
#include <stdlib.h>
#include <errnoLib.h>
#include <stdio.h>
#include <sockLib.h>
#include <inetLib.h>
#include <string.h>

#include "comm.h"
#include "tEnvoi.h"
#include "tReception.h"
#include "tJournal.h"

/* Message queue that tells the task to end */
MSG_Q_ID msg_shutdown_comm;
/* FIFO that contains every message to send to the client */
MSG_Q_ID	msg_send;
/* File descriptor of the client socket which is used in tEnvoi and tReception tasks */
SOCKET clientSock;
/* Client INET address struct which allow to get various informations like IP address, port, ... */
struct sockaddr_in clientAddr;
/* Semaphore to avoid concurrent access for a given client socket */
SEM_ID sem_clientSock;
/* Message queue that tells the task to end */
MSG_Q_ID msg_shutdown_comm;
/* FIFO that contains every message to dispatch to the tasks */
MSG_Q_ID	msg_recv_mere;
MSG_Q_ID	msg_recv_expedition;
MSG_Q_ID	msg_recv_erreur;

/* File descriptor of the server socket which is used to bind and listen connection attempts */
static SOCKET serverSock;
/* Process ID of the task tEnvoi */
static TASK pid_envoi;
/* Process ID of the task tReception */
static TASK pid_reception;

/*
 * Private function that wait until system receives a client connection
 */
ERR_COMM wait_connections()
{
	struct sockaddr_in serverAddr;
	int addr_len;
	
	/* Reset clientSock and clientAddr */
	clientSock = 0;
	bzero( (char*) &clientAddr, sizeof(clientAddr) );
		
	/* Server socket initialization */
	serverSock = socket( AF_INET, SOCK_STREAM, 0 );
	if( serverSock == ERROR )
	{
		add_log( LOG_TYPE_ERROR, "COMM", "Cannot initialize server socket." );
		perror( "Cannot initialize server socket." );
		return COMM_ERROR;
	}
		
	/* Reset structure in memory (places each byte to 0 ) */
	bzero( (char*) &serverAddr, sizeof(serverAddr) );
	
	/* Set up server sock address configuration */
	serverAddr.sin_family 		= AF_INET;
	serverAddr.sin_len 			= (u_char) sizeof(struct sockaddr_in);
	serverAddr.sin_port 		= htons( SERVER_PORT );
	serverAddr.sin_addr.s_addr 	= htonl( INADDR_ANY );
		
	if( bind( serverSock, (struct sockaddr*) &serverAddr, sizeof(struct sockaddr_in) ) == ERROR )
	{
		add_log( LOG_TYPE_ERROR, "COMM", "Cannot bind server socket." );
		perror( "Cannot bind server socket." );
		close( serverSock );
		return COMM_ERROR;
	}
		
	/* Allow MAX_CONNECTIONS connections */
	if( listen( serverSock, MAX_CONNECTIONS ) == ERROR )
	{
		add_log( LOG_TYPE_ERROR, "COMM", "Cannot listen server socket." );
		perror( "Cannot listen server socket." );
		close( serverSock );
		return COMM_ERROR;
	}
		
	/* Wait until a client connects */
	addr_len = sizeof(clientAddr);
	bzero( (char*) &clientAddr, addr_len );
			
	add_log( LOG_TYPE_NORMAL, "COMM", "Waiting for client connections..." );
	printf( "Waiting for client connections...\n" );
	clientSock = accept( serverSock, (struct sockaddr*) &clientAddr, &addr_len );
	
	{
		char buf[64];
		sprintf( buf, "New client connection (%s:%d).", inet_ntoa(clientAddr.sin_addr), 
											ntohs(clientAddr.sin_port) );
		
		add_log( LOG_TYPE_NORMAL, "COMM", buf );
	}
	
	sem_clientSock = semMCreate( SEM_Q_FIFO );
	
	pid_envoi	  = taskSpawn( "tEnvoi"		, 45, 0, 8192, (FUNCPTR) tEnvoi, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
	pid_reception = taskSpawn( "tReception"	, 50, 0, 8192, (FUNCPTR) tReception, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0  );		
	
	return COMM_OK;
}

/*
 * Private function that close the connections in releasing file descriptors
 */
void close_connections()
{
	semDelete( sem_clientSock );
	
	if( clientSock != 0 )
		close( clientSock );
	
	clientSock = 0;
	
	close( serverSock );
}

/*
 * Initialize communication module.
 * Return : COMM_CANNOT_INIT if initailization failed, COMM_OK otherwise.
 */
ERR_COMM init_comm()
{
	/* Begin by creating the pending packet queue */
	msg_send = msgQCreate( MAX_MSG_TO_SEND, PACKET_MAX_LENGTH, MSG_Q_FIFO );
	
	/* Then received packet queues */
	msg_recv_mere		= msgQCreate( MAX_MSG_TO_SEND, PACKET_MAX_LENGTH, MSG_Q_FIFO ); 
	msg_recv_expedition = msgQCreate( MAX_MSG_TO_SEND, PACKET_MAX_LENGTH, MSG_Q_FIFO );
	msg_recv_erreur		= msgQCreate( MAX_MSG_TO_SEND, PACKET_MAX_LENGTH, MSG_Q_FIFO );
	
	/* Then by creating a fifo queue that gonna tell the other tasks to shutdown */
	msg_shutdown_comm = msgQCreate( 1, 1, MSG_Q_FIFO );
	if( msg_shutdown_comm == NULL ) 
		return COMM_CANNOT_INIT;
	
	return wait_connections();	
}

/*
 * Release communication module in killing spawned tasks.
 */
void release_comm()
{
	/* Try to send a message to the tasks that must shutdown. 
	 * If it fails, force to stop the tasks.
	 * */
	if( msgQSend( msg_shutdown_comm, "S", 1, WAIT_FOREVER, 0 ) == ERROR )
	{
		taskDeleteForce( pid_envoi 	   );
		taskDeleteForce( pid_reception );
	}
	else
	{
		/* Wait until the task stops themself, but in a determined time */
		unsigned int elapsed_time = 0;
		while( taskIdVerify( pid_envoi ) == OK )
		{
			taskDelay( 1 );
		
			elapsed_time++;
			if( elapsed_time >= MAX_TIME_BEFORE_END_TASK )
				taskDeleteForce( pid_envoi );
		}
		
		elapsed_time = 0;
		while( taskIdVerify( pid_reception ) == OK )
		{
			taskDelay( 1 );
		
			elapsed_time++;
			if( elapsed_time >= MAX_TIME_BEFORE_END_TASK )
				taskDeleteForce( pid_reception );
		}
	}
	
	/* Release the different FIFOs */
	msgQDelete( msg_shutdown_comm );
	
	msgQDelete( msg_send );
	msgQDelete( msg_recv_erreur );
	msgQDelete( msg_recv_mere );
	msgQDelete( msg_recv_expedition );
	
	close_connections();
}

/* Allow user to send a packet to the different clients 
 * Param: packet_struct is the packet to send to the client
 * Return: COMM_OK if everything's went fine, COMM_NO_FIFO if the FIFO has not been initialized
 * or COMM_ERROR if the error is more complex.
 * */
ERR_COMM send_packet( PACKET_HDR* packet_struct )
{
	STATUS res;
	
	/* Try to put a new message into the FIFO.
	 * Note that the length is equal to the bigger packet to avoid a possibly loss of data.
	 * If an error occurs, an auto-check is done to know where the problem is.
	 */
	res = msgQSend( msg_send, (char*) packet_struct, PACKET_MAX_LENGTH, WAIT_FOREVER, 0 );
	
	if( res == ERROR )
	{
		switch( errnoGet() )
		{
			case S_msgQLib_INVALID_QUEUE_TYPE:
				return COMM_NO_FIFO;
			default:
				return COMM_ERROR;
		}
	}

	return COMM_OK;
}

/* Allow user to receive a packet, depending on the task asking the packet 
 * Parameters : 
 * 	- requesting_task : the task that requests the message
 *  - buffer : buffer that gonna contains the received packet
 * Returns : COMM_ERROR if task is not valid, PACKET_TYPE otherwise. 
 * */
PACKET_TYPE recv_packet( TASK requesting_task, char* buffer )
{
	PACKET_HDR* packet = NULL;
	
	switch( requesting_task )
	{
		case TASK_MERE:
			msgQReceive( msg_recv_mere, buffer, PACKET_MAX_LENGTH, WAIT_FOREVER );
			break;
		case TASK_ERROR:
			msgQReceive( msg_recv_erreur, buffer, PACKET_MAX_LENGTH, WAIT_FOREVER );
			break;
		case TASK_EXPEDITION:
			msgQReceive( msg_recv_expedition, buffer, PACKET_MAX_LENGTH, WAIT_FOREVER );
			break;
		default:
			return COMM_ERROR;
	}
	
	packet = (PACKET_HDR*) buffer;

	return packet->id_packet;
}
